package cn.lycode.generator.service.impl;

import cn.lycode.common.core.page.PageResult;
import cn.lycode.common.core.page.PageUtil;
import cn.lycode.common.enums.ErrorEnum;
import cn.lycode.common.utils.file.FileUtils;
import cn.lycode.generator.config.GeneratorConfig;
import cn.lycode.generator.core.AbstractCodeGenerationTemplate;
import cn.lycode.generator.core.CodeModelBuilder;
import cn.lycode.generator.core.util.BuildTemplateUtils;
import cn.lycode.generator.core.util.GeneratorUtils;
import cn.lycode.generator.domian.dto.DbTableListDTO;
import cn.lycode.generator.domian.dto.GeneTableDTO;
import cn.lycode.generator.domian.dto.ImportTableDTO;
import cn.lycode.generator.domian.dto.SelectTablesDTO;
import cn.lycode.generator.domian.entity.GeneratorTable;
import cn.lycode.generator.domian.entity.GeneratorTableColumn;
import cn.lycode.generator.domian.result.TableColumResult;
import cn.lycode.generator.domian.result.TableResult;
import cn.lycode.generator.domian.vo.CodeGenTempResult;
import cn.lycode.generator.domian.vo.GenCheckedInfoVO;
import cn.lycode.generator.domian.vo.GeneratorDetailVO;
import cn.lycode.generator.domian.vo.GeneratorPreviewVO;
import cn.lycode.generator.mapper.GeneratorTableColumnMapper;
import cn.lycode.generator.mapper.GeneratorTableMapper;
import cn.lycode.generator.service.GeneratorTableService;
import freemarker.template.TemplateException;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @Title: GeneratorTableServiceImpl
 * @Author lyg
 * @Date 2024/8/21 14:58
 * @description:
 */
@Service
@RequiredArgsConstructor
public class GeneratorTableServiceImpl implements GeneratorTableService {

    private final GeneratorTableMapper generatorTableMapper;
    private final GeneratorTableColumnMapper generatorTableColumnMapper;
    private final GeneratorConfig generatorConfig;

    private final FreeMarkerConfigurer configurer;

    private final JdbcTemplate jdbcTemplate;


    @Override
    public PageResult<GeneratorTable> selectDbTableNotInImport(DbTableListDTO dto) {
        PageUtil.startPage(dto);
        List<GeneratorTable> generatorTables = generatorTableMapper.selectDbTableNotInImport(dto);
        return PageUtil.getPageResult(generatorTables);
    }

    @Override
    @Transactional
    public void importTable(ImportTableDTO dto) {
        List<String> tableNames = dto.getTableNames();
        // 禁止相同table_name的记录出现多条，先执行清除操作，再生成覆盖
        generatorTableMapper.cleanTableColumnByTableName(tableNames);
        generatorTableMapper.cleanTableRecordByTableName(tableNames);
        //根据传入的表名查询数据库中的表
        List<TableResult> tableResults = generatorTableMapper.selectDbTableListByNames(tableNames);

        //根据代码中的配置设置默认值
        String moduleName = generatorConfig.getModuleName();
//        String tablePrefix = generatorConfig.getTablePrefix();
//        String[] tablePrefixArr = tablePrefix.split(",");
        String projectRootPath = System.getProperty("user.dir");
//        String pathApi = projectRootPath + File.separator + moduleName + File.separator + serviceName;
        String pathApi = projectRootPath + File.separator + moduleName;
        String pathWeb = generatorConfig.getPath().getWeb();


        GeneratorTable generatorTable;
        GeneratorTableColumn generatorTableColumn;
        List<GeneratorTableColumn> tableColumns = new ArrayList<>();

        for (TableResult table : tableResults) {
            generatorTable = GeneratorUtils.initGeneratorTable(table);
            generatorTable.setPathApi(pathApi);
            generatorTable.setModuleName(moduleName);
            generatorTable.setBusinessName(GeneratorUtils.toCamelCase(table.getTableName()));
            generatorTable.setPathWeb(pathWeb);
            generatorTable.setCreateTime(new Date());
            generatorTableMapper.insertGeneratorTable(generatorTable);
            Long tableId = generatorTable.getTableId();
            String tableName = table.getTableName();
            List<TableColumResult> tableColumResults = generatorTableMapper.selectDbTableColumnsByName(tableName);
            int i = 1;
            for (TableColumResult columResult : tableColumResults) {
                generatorTableColumn = GeneratorUtils.initColumnField(columResult, tableId, i);
                tableColumns.add(generatorTableColumn);
                i++;
            }
        }
        generatorTableColumnMapper.batchInsert(tableColumns);
    }

    @Override
    public void sync(ImportTableDTO dto) {
        importTable(dto);
    }


    @Override
    public PageResult<GeneratorTable> selectDbTableByImport(DbTableListDTO dto) {
        PageUtil.startPage(dto);
        List<GeneratorTable> generatorTables = generatorTableMapper.selectDbTableByImport(dto);
        return PageUtil.getPageResult(generatorTables);
    }

    @Override
    @Transactional
    public void delete(SelectTablesDTO dto) {
        ErrorEnum.PARAM_NULL.assertNotNull(dto.getTableNames());
        ErrorEnum.PARAM_EMPTY.assertNotNull(dto.getTableNames().isEmpty());
        //删除列和表
        generatorTableMapper.cleanTableColumnByTableName(dto.getTableNames());
        generatorTableMapper.cleanTableRecordByTableName(dto.getTableNames());
    }


    @Override
    public GeneratorDetailVO detail(String tableName) {
        GeneratorDetailVO detailVO = new GeneratorDetailVO();
        GeneratorTable generatorTable = generatorTableMapper.selectGeneratorTableByName(tableName);
        ErrorEnum.EXIST_DICT.message("table记录不存在").assertNotNull(generatorTable);
        Long tableId = generatorTable.getTableId();
        List<GeneratorTableColumn> tableColumns = generatorTableColumnMapper.getTableColumnsByTableId(tableId);
        detailVO.setColumns(tableColumns);
        detailVO.setTableInfo(generatorTable);
        return detailVO;
    }

    @Transactional
    @Override
    public void updateGeneratorSetting(GeneratorDetailVO generatorDetailVO) {
        Long tableId = generatorDetailVO.getBaseInfo().getTableId();
        int count = generatorTableMapper.countGeneratorTableById(tableId);
        ErrorEnum.INVALID_ID.assertTrue(count > 0);

        GeneratorDetailVO.BaseInfo baseInfo = generatorDetailVO.getBaseInfo();
        GeneratorDetailVO.GeneratorInfo generatorInfo = generatorDetailVO.getGeneratorInfo();

        GeneratorTable table = new GeneratorTable();
        BeanUtils.copyProperties(baseInfo, table);
        BeanUtils.copyProperties(generatorInfo, table);

        //更新配置信息
        generatorTableMapper.updateGeneratorTable(table);

        List<GeneratorDetailVO.Column> detailColumns = generatorDetailVO.getColumns();
        for (int i = 0; i < detailColumns.size(); i++) {
            GeneratorDetailVO.Column detailColumn = detailColumns.get(i);
            GeneratorTableColumn generatorTableColumn = new GeneratorTableColumn();
            BeanUtils.copyProperties(detailColumn, generatorTableColumn);
            generatorTableColumn.setSort(i + 1);
            generatorTableColumn.setSearchType(generatorTableColumn.getHtmlType());
            // 更新column设置
            generatorTableColumnMapper.updateTableColumn(generatorTableColumn);
        }
    }

    /**
     * 生成代码预览
     * @param tableName 表名
     */
    @Override
    public List<GeneratorPreviewVO> preview(String tableName) throws IOException {
        GeneratorDetailVO detailVO = detail(tableName);
        Map<String, Object> model = new CodeModelBuilder()
                .builderBaseInfo(detailVO).builderImportPackage(detailVO)
                .builderDynamicsParam(detailVO).builderJavaClass(detailVO)
                .builderVue(detailVO).builderMenuSql(detailVO).getModel();

        List<GeneratorPreviewVO> previews = new ArrayList<>();
        // 处理 API 模板
        handleTemplates(BuildTemplateUtils.getApiTemplates(configurer, detailVO.getGeneratorInfo().getPathApi(), detailVO, model), previews, model);
        // 处理 Web 模板
        handleTemplates(BuildTemplateUtils.getWebTemplates(configurer, detailVO.getGeneratorInfo().getPathWeb(), detailVO, model), previews, model);
        // 处理 Sql模板
        handleTemplates(BuildTemplateUtils.getSqlTemplates(configurer, "", detailVO, model), previews, model);
        return previews;
    }



    /**
     * 由templates生成代码，放入预览列表中
     */
    private void handleTemplates(List<AbstractCodeGenerationTemplate> templates, List<GeneratorPreviewVO> previews, Map<String, Object> model) throws IOException {
        for (AbstractCodeGenerationTemplate template : templates) {
            CodeGenTempResult tmpRes = template.buildTemplate(false, false);
            String relativePath = tmpRes.getRelativePath();
            String fileName = Paths.get(relativePath).getFileName().toString();
            String templateProcess = renderTemplateString(tmpRes, model);

            GeneratorPreviewVO previewVO = new GeneratorPreviewVO();
            previewVO.setCode(templateProcess);
            previewVO.setName(fileName);
            previewVO.setLanguage(tmpRes.getLanguage());
            previewVO.setAlias(tmpRes.getAlias());
            previews.add(previewVO);
        }
    }

    @Override
    public byte[] downloadZip(SelectTablesDTO dto) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        List<String> tableNames = dto.getTableNames();
        // 根据id 获取要导入的表detail
        List<GeneratorDetailVO> detailVOS = getDetailsForTables(tableNames);

        for (GeneratorDetailVO detailVO : detailVOS) {
            CodeModelBuilder modelBuilder = new CodeModelBuilder();
            String rootPathApi = detailVO.getGeneratorInfo().getPathApi();
            String rootPathWeb = detailVO.getGeneratorInfo().getPathWeb();
            Map<String, Object> model = modelBuilder
                    .builderBaseInfo(detailVO).builderImportPackage(detailVO)
                    .builderDynamicsParam(detailVO).builderJavaClass(detailVO)
                    .builderVue(detailVO).builderMenuSql(detailVO).getModel();

            List<AbstractCodeGenerationTemplate> apiTemplates = BuildTemplateUtils.getApiTemplates(configurer, rootPathApi, detailVO, model);
            for (AbstractCodeGenerationTemplate apiTemplate : apiTemplates) {
                CodeGenTempResult apiTmpRes = apiTemplate.buildTemplate(false, false);
                addFileToZip(zip, apiTmpRes, model);
            }

            List<AbstractCodeGenerationTemplate> webTemplates = BuildTemplateUtils.getWebTemplates(configurer, rootPathWeb, detailVO, model);
            for (AbstractCodeGenerationTemplate webTemplate : webTemplates) {
                CodeGenTempResult webTmpRes = webTemplate.buildTemplate(false, false);
                addFileToZip(zip, webTmpRes, model);
            }

            List<AbstractCodeGenerationTemplate> sqlTemplates = BuildTemplateUtils.getSqlTemplates(configurer, "", detailVO, model);
            for (AbstractCodeGenerationTemplate webTemplate : sqlTemplates) {
                CodeGenTempResult sqlTmpRes = webTemplate.buildTemplate(false, false);
                addFileToZip(zip, sqlTmpRes, model);
            }
        }
        zip.close();
        return outputStream.toByteArray();
    }

    @Override
    public GenCheckedInfoVO checkDist(String tableName) {
        GeneratorDetailVO detailVO = detail(tableName);
        String pathApi = detailVO.getGeneratorInfo().getPathApi();
        String pathWeb = detailVO.getGeneratorInfo().getPathWeb();
        boolean apiPathExists = FileUtils.isPathExists(pathApi);
        boolean webPathExists = FileUtils.isPathExists(pathWeb);
        GenCheckedInfoVO checkedInfo = new GenCheckedInfoVO();
        // 如果选择模版包含前端，进行前端校验
        if (("all").equals(detailVO.getGeneratorInfo().getGenerateType())) {
            checkedInfo.setCheckedWebPath(webPathExists);
        }
        checkedInfo.setCheckedApiPath(apiPathExists);
        checkedInfo.setPathApi(pathApi);
        checkedInfo.setPathWeb(pathWeb);
        return checkedInfo;
    }

    @Override
    public List<String> generator(GeneTableDTO dto) throws IOException {
        List<String> messages = new ArrayList<>();
        GeneratorDetailVO detailVO = detail(dto.getTableName());
        CodeModelBuilder modelBuilder = new CodeModelBuilder();
        String rootPathApi = detailVO.getGeneratorInfo().getPathApi();
        String rootPathWeb = detailVO.getGeneratorInfo().getPathWeb();
        Map<String, Object> model = modelBuilder
                .builderBaseInfo(detailVO).builderImportPackage(detailVO)
                .builderDynamicsParam(detailVO).builderJavaClass(detailVO)
                .builderVue(detailVO).builderMenuSql(detailVO).getModel();
        List<AbstractCodeGenerationTemplate> apiTemplates = BuildTemplateUtils.getApiTemplates(configurer, rootPathApi, detailVO, model);
        for (AbstractCodeGenerationTemplate apiTemplate : apiTemplates) {
            CodeGenTempResult result = apiTemplate.buildTemplate(true, dto.isCover());
            messages.add(result.getOutputMessage());
        }
        List<AbstractCodeGenerationTemplate> webTemplates = BuildTemplateUtils.getWebTemplates(configurer, rootPathWeb, detailVO, model);
        for (AbstractCodeGenerationTemplate webTemplate : webTemplates) {
            CodeGenTempResult result = webTemplate.buildTemplate(true, dto.isCover());
            messages.add(result.getOutputMessage());
        }

        List<AbstractCodeGenerationTemplate> sqlTemplates = BuildTemplateUtils.getSqlTemplates(configurer, "", detailVO, model);
        for (AbstractCodeGenerationTemplate sqlTemplate : sqlTemplates) {
            CodeGenTempResult result = sqlTemplate.buildTemplate(false, false);
            String templateProcess = renderTemplateString(result, model);
            if (templateProcess != null && templateProcess.length() > 0) {
                jdbcTemplate.execute(templateProcess);
            }
            messages.add(result.getOutputMessage());

        }

        return messages;
    }


    private List<GeneratorDetailVO> getDetailsForTables(List<String> tableNames) {
        List<GeneratorDetailVO> detailVOS = new ArrayList<>();
        for (String tableName : tableNames) {
            GeneratorDetailVO detail = detail(tableName);
            detailVOS.add(detail);
        }
        return detailVOS;
    }

    private void addFileToZip(ZipOutputStream zip, CodeGenTempResult tempResult, Map<String, Object> model) throws IOException {
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(renderTemplate(tempResult, model))) {
            String relativePath = tempResult.getRelativePath();
            ZipEntry zipEntry = new ZipEntry(relativePath);
            zip.putNextEntry(zipEntry);
            IOUtils.copy(inputStream, zip);
            zip.closeEntry();
        }
    }

    private byte[] renderTemplate(CodeGenTempResult tempResult, Map<String, Object> model) throws IOException {
        return renderTemplateString(tempResult, model).getBytes(StandardCharsets.UTF_8);
    }

    private String renderTemplateString(CodeGenTempResult tempResult, Map<String, Object> model) throws IOException {
        try (StringWriter writer = new StringWriter()) {
            tempResult.getTemplate().process(model, writer);
            return writer.toString();
        } catch (TemplateException e) {
            throw new IOException("Error rendering template", e);
        }
    }

}
